/**
 ******************************************************************************
 * @file    bl702_i2s.c
 * @version V1.0
 * @date
 * @brief   This file is the standard driver c file
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; COPYRIGHT(c) 2020 Bouffalo Lab</center></h2>
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *   1. Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright notice,
 *      this list of conditions and the following disclaimer in the documentation
 *      and/or other materials provided with the distribution.
 *   3. Neither the name of Bouffalo Lab nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************
 */

#include "bl702_i2s.h"
#include "bl702_glb.h"

/** @addtogroup  BL702_Peripheral_Driver
 *  @{
 */

/** @addtogroup  I2S
 *  @{
 */

/** @defgroup  I2S_Private_Macros
 *  @{
 */

/*@} end of group I2S_Private_Macros */

/** @defgroup  I2S_Private_Types
 *  @{
 */

/*@} end of group I2S_Private_Types */

/** @defgroup  I2S_Private_Variables
 *  @{
 */

/*@} end of group I2S_Private_Variables */

/** @defgroup  I2S_Global_Variables
 *  @{
 */

/*@} end of group I2S_Global_Variables */

/** @defgroup  I2S_Private_Fun_Declaration
 *  @{
 */

/*@} end of group I2S_Private_Fun_Declaration */

/** @defgroup  I2S_Private_Functions
 *  @{
 */

/*@} end of group I2S_Private_Functions */

/** @defgroup  I2S_Public_Functions
 *  @{
 */

/****************************************************************************/ /**
                                                                                * @brief  I2S BCLK config
                                                                                *
                                                                                * @param  i2sCfg: I2S configuration pointer
                                                                                *
                                                                                * @return NONE
                                                                                *
                                                                                *******************************************************************************/
void I2S_SetBclkPeriod(I2S_CFG_Type *i2sCfg) {
  uint32_t overSampleRate;
  uint32_t bclkDivCnt;
  uint32_t tmpVal;

  CHECK_PARAM(IS_I2S_FRAME_SIZE_TYPE(i2sCfg->frameSize));

  overSampleRate = i2sCfg->audioFreqHz / i2sCfg->sampleFreqHz;

  switch (i2sCfg->frameSize) {
  case I2S_SIZE_FRAME_8:
    bclkDivCnt = overSampleRate / 16;
    break;

  case I2S_SIZE_FRAME_16:
    bclkDivCnt = overSampleRate / 32;
    break;

  case I2S_SIZE_FRAME_24:
    bclkDivCnt = overSampleRate / 48;
    break;

  case I2S_SIZE_FRAME_32:
    bclkDivCnt = overSampleRate / 64;
    break;

  default:
    bclkDivCnt = overSampleRate / 16;
    break;
  }

  bclkDivCnt = (bclkDivCnt / 2) - 1;

  tmpVal = (bclkDivCnt << 16) | bclkDivCnt;
  BL_WR_REG(I2S_BASE, I2S_BCLK_CONFIG, tmpVal);
}

/****************************************************************************/ /**
                                                                                * @brief  I2S configuration
                                                                                *
                                                                                * @param  i2sCfg: I2S configuration pointer
                                                                                *
                                                                                * @return NONE
                                                                                *
                                                                                *******************************************************************************/
void I2S_Init(I2S_CFG_Type *i2sCfg) {
  uint32_t tmpVal;

  /* Check the parameters */
  CHECK_PARAM(IS_I2S_ENDIAN_TYPE(i2sCfg->endianType));
  CHECK_PARAM(IS_I2S_MODE_TYPE(i2sCfg->modeType));
  CHECK_PARAM(IS_I2S_FRAME_SIZE_TYPE(i2sCfg->frameSize));
  CHECK_PARAM(IS_I2S_FS_MODE_TYPE(i2sCfg->fsMode));
  CHECK_PARAM(IS_I2S_FS_CHANNEL_TYPE(i2sCfg->fsChannel));
  CHECK_PARAM(IS_I2S_DATA_SIZE_TYPE(i2sCfg->dataSize));
  CHECK_PARAM(IS_I2S_MONO_MODE_CHANNEL(i2sCfg->monoModeChannel));

  /* Disable clock gate */
  GLB_AHB_Slave1_Clock_Gate(DISABLE, BL_AHB_SLAVE1_I2S);

  tmpVal = BL_RD_REG(I2S_BASE, I2S_CONFIG);

  /* Set data endian*/
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_ENDIAN, i2sCfg->endianType);

  /* Set I2S mode */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_I2S_MODE, i2sCfg->modeType);

  /* Set BCLK invert */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_I2S_BCLK_INV, i2sCfg->bclkInvert);

  /* Set FS size */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_FRAME_SIZE, i2sCfg->frameSize);

  /* Set FS invert */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_I2S_FS_INV, i2sCfg->fsInvert);

  /* Set FS mode */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_FS_1T_MODE, i2sCfg->fsMode);

  /* Set FS channel mode */

  switch (i2sCfg->fsChannel) {
  case I2S_FS_CHANNELS_2:
    tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_FS_3CH_MODE);
    tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_FS_4CH_MODE);
    break;

  case I2S_FS_CHANNELS_3:
    tmpVal = BL_SET_REG_BIT(tmpVal, I2S_CR_FS_3CH_MODE);
    tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_FS_4CH_MODE);
    break;

  case I2S_FS_CHANNELS_4:
    tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_FS_3CH_MODE);
    tmpVal = BL_SET_REG_BIT(tmpVal, I2S_CR_FS_4CH_MODE);
    break;

  default:
    break;
  }

  /* Set Data size */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_DATA_SIZE, i2sCfg->dataSize);

  /* Set Data offset */
  if (i2sCfg->dataOffset != 0) {
    tmpVal = BL_SET_REG_BIT(tmpVal, I2S_CR_OFS_EN);
    tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_OFS_CNT, i2sCfg->dataOffset - 1);
  } else {
    tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_OFS_EN);
  }

  /* Set mono mode */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_MONO_MODE, i2sCfg->monoMode);

  /* Set rx mono mode channel left or right */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_MONO_RX_CH, i2sCfg->monoModeChannel);

  /* Clear mute mode */
  tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_MUTE_MODE);

  BL_WR_REG(I2S_BASE, I2S_CONFIG, tmpVal);

  I2S_SetBclkPeriod(i2sCfg);
}

/****************************************************************************/ /**
                                                                                * @brief  I2S configure FIFO function
                                                                                *
                                                                                * @param  fifoCfg: FIFO configuration structure pointer
                                                                                *
                                                                                * @return None
                                                                                *
                                                                                *******************************************************************************/
void I2S_FifoConfig(I2S_FifoCfg_Type *fifoCfg) {
  uint32_t tmpVal;

  tmpVal = BL_RD_REG(I2S_BASE, I2S_FIFO_CONFIG_0);
  /* Set packed mode */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_FIFO_LR_MERGE, fifoCfg->lRMerge);
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_FIFO_LR_EXCHG, fifoCfg->frameDataExchange);
  /* Clear tx and rx FIFO signal */
  tmpVal = BL_SET_REG_BIT(tmpVal, I2S_TX_FIFO_CLR);
  tmpVal = BL_SET_REG_BIT(tmpVal, I2S_RX_FIFO_CLR);

  /* Set DMA config */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_DMA_TX_EN, fifoCfg->txfifoDmaEnable);
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_DMA_RX_EN, fifoCfg->rxfifoDmaEnable);

  BL_WR_REG(I2S_BASE, I2S_FIFO_CONFIG_0, tmpVal);

  /* Set CLR signal to 0*/
  tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_TX_FIFO_CLR);
  tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_RX_FIFO_CLR);
  BL_WR_REG(I2S_BASE, I2S_FIFO_CONFIG_0, tmpVal);

  tmpVal = BL_RD_REG(I2S_BASE, I2S_FIFO_CONFIG_1);
  /* Set TX and RX FIFO threshold */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_TX_FIFO_TH, fifoCfg->txFifoLevel);
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_RX_FIFO_TH, fifoCfg->rxFifoLevel);

  BL_WR_REG(I2S_BASE, I2S_FIFO_CONFIG_1, tmpVal);
}

/****************************************************************************/ /**
                                                                                * @brief  I2S configure IO function
                                                                                *
                                                                                * @param  ioCfg: IO configuration structure pointer
                                                                                *
                                                                                * @return None
                                                                                *
                                                                                *******************************************************************************/
void I2S_IOConfig(I2S_IOCfg_Type *ioCfg) {
  uint32_t tmpVal;

  tmpVal = BL_RD_REG(I2S_BASE, I2S_IO_CONFIG);
  /* Enable or disable deglitch */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_DEG_EN, ioCfg->deglitchEn);

  /* Set deglitch cycle count */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_DEG_CNT, ioCfg->deglitchCnt);

  /* Enable or disable inverse BCLK signal */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_I2S_BCLK_INV, ioCfg->inverseBCLK);

  /* Enable or disable inverse FS signal */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_I2S_FS_INV, ioCfg->inverseFS);

  /* Enable or disable inverse RX signal */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_I2S_RXD_INV, ioCfg->inverseRX);

  /* Enable or disable inverse TX signal */
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_I2S_TXD_INV, ioCfg->inverseTX);

  BL_WR_REG(I2S_BASE, I2S_IO_CONFIG, tmpVal);
}

/****************************************************************************/ /**
                                                                                * @brief  Enable I2S
                                                                                *
                                                                                * @param  roleType: I2S master or slave
                                                                                *
                                                                                * @return None
                                                                                *
                                                                                *******************************************************************************/
void I2S_Enable(I2S_Role_Type roleType) {
  uint32_t tmpVal;

  /* Check the parameters */
  CHECK_PARAM(IS_I2S_ROLE_TYPE(roleType));

  tmpVal = BL_RD_REG(I2S_BASE, I2S_CONFIG);
  tmpVal = BL_SET_REG_BIT(tmpVal, I2S_CR_I2S_TXD_EN);
  tmpVal = BL_SET_REG_BIT(tmpVal, I2S_CR_I2S_RXD_EN);

  /* Set role type */
  if (I2S_ROLE_MASTER == roleType) {
    tmpVal = BL_SET_REG_BIT(tmpVal, I2S_CR_I2S_M_EN);
    tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_I2S_S_EN);
  } else if (I2S_ROLE_SLAVE == roleType) {
    tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_I2S_M_EN);
    tmpVal = BL_SET_REG_BIT(tmpVal, I2S_CR_I2S_S_EN);
  }

  BL_WR_REG(I2S_BASE, I2S_CONFIG, tmpVal);
}

/****************************************************************************/ /**
                                                                                * @brief  Disable I2S
                                                                                *
                                                                                * @param  None
                                                                                *
                                                                                * @return None
                                                                                *
                                                                                *******************************************************************************/
void I2S_Disable(void) {
  uint32_t tmpVal;

  tmpVal = BL_RD_REG(I2S_BASE, I2S_CONFIG);
  tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_I2S_TXD_EN);
  tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_I2S_RXD_EN);

  tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_I2S_M_EN);
  tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_I2S_S_EN);
  BL_WR_REG(I2S_BASE, I2S_CONFIG, tmpVal);
}

/****************************************************************************/ /**
                                                                                * @brief  I2S read data
                                                                                *
                                                                                * @param  None
                                                                                *
                                                                                * @return Data read
                                                                                *
                                                                                *******************************************************************************/
uint32_t I2S_Read(void) {
  while (0 == BL_GET_REG_BITS_VAL(BL_RD_REG(I2S_BASE, I2S_FIFO_CONFIG_1), I2S_RX_FIFO_CNT)) {
  };

  return BL_RD_REG(I2S_BASE, I2S_FIFO_RDATA);
}

/****************************************************************************/ /**
                                                                                * @brief  I2S write data
                                                                                *
                                                                                * @param  data: write data
                                                                                *
                                                                                * @return None
                                                                                *
                                                                                *******************************************************************************/
void I2S_Write(uint32_t data) {
  while (0 == BL_GET_REG_BITS_VAL(BL_RD_REG(I2S_BASE, I2S_FIFO_CONFIG_1), I2S_TX_FIFO_CNT)) {
  };

  BL_WR_REG(I2S_BASE, I2S_FIFO_WDATA, data);
}

/****************************************************************************/ /**
                                                                                * @brief  I2S set mute
                                                                                *
                                                                                * @param  enabled: mute enabled or not
                                                                                *
                                                                                * @return None
                                                                                *
                                                                                *******************************************************************************/
void I2S_Mute(BL_Fun_Type enabled) {
  uint32_t tmpVal;

  tmpVal = BL_RD_REG(I2S_BASE, I2S_CONFIG);

  if (enabled ? 1 : 0) {
    tmpVal = BL_SET_REG_BIT(tmpVal, I2S_CR_MUTE_MODE);
  } else {
    tmpVal = BL_CLR_REG_BIT(tmpVal, I2S_CR_MUTE_MODE);
  }

  BL_WR_REG(I2S_BASE, I2S_CONFIG, tmpVal);
}

/****************************************************************************/ /**
                                                                                * @brief  I2S set 24-bit data align mode in fifo
                                                                                *
                                                                                * @param  justType: Align mode
                                                                                *
                                                                                * @return None
                                                                                *
                                                                                *******************************************************************************/
void I2S_SetFifoJustified(I2S_FIFO_24_Justified_Type justType) {
  uint32_t tmpVal;

  /* Check the parameters */
  CHECK_PARAM(IS_I2S_FIFO_24_JUSTIFIED_TYPE(justType));

  tmpVal = BL_RD_REG(I2S_BASE, I2S_FIFO_CONFIG_0);
  tmpVal = BL_SET_REG_BITS_VAL(tmpVal, I2S_CR_FIFO_24B_LJ, justType);
  BL_WR_REG(I2S_BASE, I2S_FIFO_CONFIG_0, tmpVal);
}

/****************************************************************************/ /**
                                                                                * @brief  I2S flush
                                                                                *
                                                                                * @param  None
                                                                                *
                                                                                * @return data count in TX FIFO
                                                                                *
                                                                                *******************************************************************************/
uint32_t I2S_GetTxFIFO_AvlCnt(void) { return BL_GET_REG_BITS_VAL(BL_RD_REG(I2S_BASE, I2S_FIFO_CONFIG_1), I2S_TX_FIFO_CNT); }

/****************************************************************************/ /**
                                                                                * @brief  I2S flush
                                                                                *
                                                                                * @param  None
                                                                                *
                                                                                * @return data count in RX FIFO
                                                                                *
                                                                                *******************************************************************************/
uint32_t I2S_GetRxFIFO_AvlCnt(void) { return BL_GET_REG_BITS_VAL(BL_RD_REG(I2S_BASE, I2S_FIFO_CONFIG_1), I2S_RX_FIFO_CNT); }

/****************************************************************************/ /**
                                                                                * @brief  I2S flush
                                                                                *
                                                                                * @param  None
                                                                                *
                                                                                * @return None
                                                                                *
                                                                                *******************************************************************************/
void I2S_Flush(void) {
  while (I2S_TX_FIFO_SIZE != I2S_GetTxFIFO_AvlCnt())
    ;
}

/*@} end of group I2S_Public_Functions */

/*@} end of group I2S */

/*@} end of group BL702_Peripheral_Driver */
